package com.tom.spring.formework.context;

import com.tom.spring.formework.annotation.Autowired;
import com.tom.spring.formework.annotation.Controller;
import com.tom.spring.formework.annotation.Service;
import com.tom.spring.formework.aop.AopConfig;
import com.tom.spring.formework.beans.BeanDefinition;
import com.tom.spring.formework.beans.BeanPostProcessor;
import com.tom.spring.formework.beans.BeanWrapper;
import com.tom.spring.formework.context.support.BeanDefinitionReader;
import com.tom.spring.formework.core.BeanFactory;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author : tom
 * @project: tom-spring
 * @date : 2018/9/25 15:54
 */
public class ApplicationContext extends DefaultListableBeanFactory implements BeanFactory {

    private String[] configLocations;
    //保存 bean信息
    private BeanDefinitionReader reader;
    //用来保证注册式单例的容器
    private Map<String, Object> beanCacheMap = new HashMap<String, Object>();
    //用来存储所有的被代理过的对象
    private Map<String, BeanWrapper> beanWrapperMap = new ConcurrentHashMap<String, BeanWrapper>();

    public ApplicationContext(String... configLocations) {
        this.configLocations = configLocations;
        refresh();
    }

    public void refresh() {
        //定位
        this.reader = new BeanDefinitionReader(configLocations);
        //加载
        List<String> beanDefinitions = reader.loadBeanDefinitions();
        //注册
        doRegisty(beanDefinitions);
        //自动化的依赖注入
        doAutowrited();

        /*ManagerServiceImpl managerService = (ManagerServiceImpl) this.getBean("manager_service_tom");
        UserServiceImpl userService = (UserServiceImpl) this.getBean("userServiceImpl");
        managerService.getManager();
        userService.get();*/
    }

    private void doAutowrited() {
        //开始执行自动化依赖注入

        //实例化
        for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : beanDefinitionMap.entrySet()) {
            String beanName = beanDefinitionEntry.getKey();
            if (!beanDefinitionEntry.getValue().isLazyInit()) {
                getBean(beanName);
            }
        }

        //注入
        for (Map.Entry<String, BeanWrapper> beanWrapperEntry : this.beanWrapperMap.entrySet()) {
            populateBean(beanWrapperEntry.getKey(), beanWrapperEntry.getValue().getOriginalInstance());
        }

    }

    /**
     * spring di 依赖注入过程
     *
     * @param beanName
     * @param inalInstance
     */
    private void populateBean(String beanName, Object inalInstance) {
        Class clazz = inalInstance.getClass();
        if (!(clazz.isAnnotationPresent(Controller.class) || clazz.isAnnotationPresent(Service.class))) {
            return;
        }

        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            if (!field.isAnnotationPresent(Autowired.class)) {
                continue;
            }
            Autowired autowired = field.getAnnotation(Autowired.class);
            String autowiredBeanName = autowired.value().trim();
            if ("".equals(autowiredBeanName)) {
                autowiredBeanName = field.getType().getName();
            }
            field.setAccessible(true);
            try {
                field.set(inalInstance, this.beanWrapperMap.get(autowiredBeanName).getWrappedInstance());
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }

    }

    /**
     * 注册
     * 真正的将BeanDefinitions注册到beanDefinitionMap中
     *
     * @param beanDefinitions
     */
    private void doRegisty(List<String> beanDefinitions) {

        /**
         *beanName有三种情况:
         *1、默认是类名首字母小写
         *2、自定义名字
         *3、接口注入
         */
        for (String className : beanDefinitions) {
            try {
                Class<?> beanClass = Class.forName(className);
                //如果是接口 是不实例化的 实例化子类
                if (beanClass.isInterface()) {
                    continue;
                }
                BeanDefinition beanDefinition = this.reader.registerBean(className);
                if (beanDefinition != null) {
                    String key = beanDefinition.getFactoryBeanName();
                    if (beanClass.isAnnotationPresent(Service.class)) {
                        Service service = beanClass.getAnnotation(Service.class);
                        if (service != null) {
                            key = service.value();
                        }
                    } else {
                        key = beanDefinition.getFactoryBeanName();
                    }
                    this.beanDefinitionMap.put(key, beanDefinition);
                }

                Class<?>[] interfaces = beanClass.getInterfaces();

                for (Class<?> anInterface : interfaces) {
                    this.beanDefinitionMap.put(anInterface.getName(), beanDefinition);
                }
                //到这里为止，容器初始化完毕
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }


    /**
     * 依赖注入，从这里开始，通过读取BeanDefinition中的信息，然后通过发射机制创建一个实例对象返回
     * spring做法是不会吧最原始对象放进去，会用一个BeanWrapper来进行一次包装
     * 修饰器模式
     * 1，保留原来的OOP关系
     * 2，我需要对它进行扩展，增强(为了以后的AOP打下基础)
     *
     * @param beanName
     * @return
     */
    @Override
    public Object getBean(String beanName) {
        BeanDefinition beanDefinition = this.beanDefinitionMap.get(beanName);
        BeanPostProcessor beanPostProcessor = new BeanPostProcessor();
        Object instance = instantionBean(beanDefinition);
        if (null == instance) {
            return null;
        }
        //在实例化初始化之前调用一次
        beanPostProcessor.postProcessBeforeInitialization(instance, beanName);

        BeanWrapper beanWrapper = new BeanWrapper(instance);
        try {
            beanWrapper.setAopConfig(instantionAopConfig(beanDefinition));
        } catch (Exception e) {
            e.printStackTrace();
        }
        beanWrapper.setPostProcessor(beanPostProcessor);
        this.beanWrapperMap.put(beanName, beanWrapper);

        beanPostProcessor.postProcessAfterInitialization(instance, beanName);

        return this.beanWrapperMap.get(beanName).getWrappedInstance();
    }

    private AopConfig instantionAopConfig(BeanDefinition beanDefinition) throws Exception {
        AopConfig aopConfig = new AopConfig();
        String expression = this.reader.getConfig().getProperty("pointCut");
        String[] before = this.reader.getConfig().getProperty("aspectBefore").split("\\s");
        String[] after = this.reader.getConfig().getProperty("aspectAfter").split("\\s");

        String className = beanDefinition.getBeanClassName();
        Class<?> clazz = Class.forName(className);

        Pattern pattern = Pattern.compile(expression);
        Class aspectClass = Class.forName(before[0]);

        for (Method method : clazz.getMethods()) {
            Matcher matcher = pattern.matcher(method.toString());
            if (matcher.matches()) {
                aopConfig.put(method, aspectClass.newInstance(), new Method[]{aspectClass.getMethod(before[1]), aspectClass.getMethod(after[1])});
            }
        }
        return aopConfig;
    }

    /**
     * 返回原始Bean
     *
     * @param beanDefinition
     * @return
     */
    private Object instantionBean(BeanDefinition beanDefinition) {
        Object instance = null;
        try {
            String classsName = beanDefinition.getBeanClassName();
            if (this.beanCacheMap.containsKey(classsName)) {
                instance = this.beanCacheMap.get(classsName);
            } else {
                Class<?> clazz = Class.forName(classsName);
                instance = clazz.newInstance();

                beanCacheMap.put(classsName, instance);
            }
            return instance;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public String[] getBeanDefinitionNames() {
        return this.beanDefinitionMap.keySet().toArray(new String[this.beanDefinitionMap.size()]);
    }


    public Properties getConfig() {
        return this.reader.getConfig();
    }
}
